################################################################################
#
#Copyright (c) 2012, Petr Skramovsky
#All rights reserved.
#
#Redistribution and use in source and binary forms, with or without
#modification, are permitted provided that the following conditions are met:
#    * Redistributions of source code must retain the above copyright
#      notice, this list of conditions and the following disclaimer.
#    * Redistributions in binary form must reproduce the above copyright
#      notice, this list of conditions and the following disclaimer in the
#      documentation and/or other materials provided with the distribution.
#
#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
#ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
#WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
#DISCLAIMED. IN NO EVENT SHALL Petr Skramovsky BE LIABLE FOR ANY
#DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
#(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
#LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
#ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
#(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
#SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
################################################################################

import c4d
import demo
import c4d.utils
import os
import md3
import math
from qsystem import QFileSystem

PLA_COMMAND = 12421
IMG_EXTS = [ ".jpg", ".jpeg", ".png", ".tga" ]

def weapon_id2md3path(id):
    if id == 1:
        return "models/weapons2/gauntlet/gauntlet.md3"
    elif id == 2:
        return "models/weapons2/machinegun/machinegun.md3"
    elif id == 3:
        return "models/weapons2/shotgun/shotgun.md3"
    elif id == 4:
        return "models/weapons2/grenadel/grenadel.md3"
    elif id == 5:
        return "models/weapons2/rocketl/rocketl.md3"
    elif id == 6:
        return "models/weapons2/lightning/lightning.md3"
    elif id == 7:
        return "models/weapons2/railgun/railgun.md3"
    elif id == 8:
        return "models/weapons2/plasma/plasma.md3"
    elif id == 9:
        return "models/weapons2/bfg/bfg.md3"
    return -1
    
    
def md3_make_material(doc,fs,tex):
  name = tex.split("/")[-1]
  if doc.SearchMaterial(name):
    return doc.SearchMaterial(name)
  rname = tex.split(".")[0]
  fex = ""
  for ex in IMG_EXTS:
    if fs[ rname + ex ]:
      fex = ex
      break
  epath = os.path.join( doc.GetDocumentPath(), rname.replace("/","\\") + ex )
  fs.extract( rname + ex, doc.GetDocumentPath() )
  mat = c4d.BaseMaterial(c4d.Mmaterial)
  shd = c4d.BaseList2D(c4d.Xbitmap)
  shd[c4d.BITMAPSHADER_FILENAME] = epath
  mat[c4d.MATERIAL_COLOR_SHADER] = shd
  mat[c4d.MATERIAL_USE_SPECULAR] = False
  mat.SetName(name)
  mat.InsertShader(shd)
  mat.Message(c4d.MSG_UPDATE)
  mat.Update(True, True)
  return mat

def c4d_make_model_static(fs,name,skincolor="default"):
    m = fs[name]
    if not m:
        return
    skin = fs[name.split(".")[0] + "_" + skincolor + ".skin"]
    skin_tex = {}
    if skin:
        sp = skin.split()
        for i in range(0,m.num_surfaces):
            s, t = sp[i].split(",")
            skin_tex[s] = t
    
    root_object = c4d.BaseObject(c4d.Onull)
    root_object.SetName(name)
    root_object.SetAbsRot(c4d.Vector(0,math.pi/2,0))
  
    ps = []
    for s in m.surfaces:
        p = c4d.PolygonObject(s.num_verts, s.num_triangles)
        p.SetName(s.name)
        uv = c4d.UVWTag(s.num_triangles)
        uv[c4d.UVWTAG_LOCK] = True
        p.SetPhong(True, 1, math.radians(80.0))
        p.InsertTag(uv)
        #if skin:
        #    tex = skin_tex[s.name]
        #else:
        #    tex = s.shaders[0].name
        tex = s.shaders[0].name
        mat = md3_make_material(doc,fs,tex)
        doc.InsertMaterial(mat)
        mtag = c4d.TextureTag()
        mtag.SetMaterial(mat)
        mtag[c4d.TEXTURETAG_PROJECTION] = c4d.TEXTURETAG_PROJECTION_UVW
        p.InsertTag(mtag)
        for i in range(s.num_triangles):
            t = s.triangles[i]
            p.SetPolygon(i, c4d.CPolygon(t.a,t.b,t.c))
            uv.SetSlow(i, c4d.Vector(s.st[t.a].s, s.st[t.a].t, 0), c4d.Vector(s.st[t.b].s, s.st[t.b].t, 0), c4d.Vector(s.st[t.c].s, s.st[t.c].t, 0), c4d.Vector())
        p.InsertUnder(root_object)
        ps.append(p)
    
    for i in range(0,m.num_tags):
        tag = m.tags[i]
        tag_object = c4d.BaseObject(c4d.Onull)
        tag_object.SetName(tag.name)
        tag_object.InsertUnder(root_object)
        
    return root_object

def c4d_find_child(base,child):
    for o in base.GetChildren():
        if child == o.GetName():
            return o
    return None

def c4d_make_const(src,target):
    const = c4d.BaseTag(1019364)
    const[c4d.ID_CA_CONSTRAINT_TAG_PSR] = 1
    const[10001] = target
    src.InsertTag(const)
    
def c4d_make_player_static(fs,player_object,model,weapons,skincolor="default"):    
    model_object = c4d.BaseObject(c4d.Onull)
    model_object.SetName(model)
    head = c4d_make_model_static(fs,"models/players/%s/head.md3" % model,skincolor)
    upper = c4d_make_model_static(fs,"models/players/%s/upper.md3" % model,skincolor)
    lower = c4d_make_model_static(fs,"models/players/%s/lower.md3" % model,skincolor)
    c4d_make_const(head,c4d_find_child(upper,"tag_head"))
    c4d_make_const(upper,c4d_find_child(lower,"tag_torso"))
    head.InsertUnder(model_object)
    upper.InsertUnder(model_object)
    lower.InsertUnder(model_object)
    model_object.InsertUnder(player_object)
    
    weapons_object = c4d.BaseObject(c4d.Onull)
    weapons_object.SetName("weapons")
    for w in weapons:
        weapon = c4d_make_model_static(fs,weapon_id2md3path(w))
        weapon.SetEditorMode(c4d.MODE_OFF)
        weapon.SetRenderMode(c4d.MODE_OFF)
        weapon.InsertUnder(weapons_object)
        c4d_make_const(weapon,c4d_find_child(upper,"tag_weapon"))
    weapons_object.InsertUnder(player_object)

def c4d_matrix_hpb(md3mat):
    v1 = c4d.Vector(md3mat[0].x, md3mat[0].y, md3mat[0].z)
    v2 = c4d.Vector(md3mat[1].x, md3mat[1].y, md3mat[1].z)
    v3 = c4d.Vector(md3mat[2].x, md3mat[2].y, md3mat[2].z)
    m = c4d.Matrix(c4d.Vector(0),v1,v2,v3)
    return c4d.utils.MatrixToHPB(m)

def c4d_make_player_camera(player_object):
    cam = c4d.BaseObject(c4d.Ocamera)
    cam.SetName("1st person")
    cam.SetRelPos( c4d.Vector( 0, 50, 0 ) )
    cam.InsertUnder(player_object)

def unsnap_vector(prev,now):
    x, y, z = now
    if x == 0:
        x = prev[0]
    if y == 0:
        y = prev[1]
    if z == 0:
        z = prev[2]
    return [x,y,z]

def unsnap_num(prev,now):
    if now == 0:
        return prev
    return now

def time2fps(starttime, fps, time):
    return int( ( ( time - starttime ) / 1000.0 ) * fps )

def c4d_animate_player(doc,fs,anim,frame,fps,player_object,model,pps,ps):
    if not pps:
        px, py, pz = ps.origin
        rx, ry, rz = ps.viewangles
        torsoAnim = ps.torsoAnim
        legsAnim = ps.legsAnim
    else:
        px, py, pz = ps.origin = unsnap_vector(pps.origin, ps.origin)
        rx, ry, rz = ps.viewangles = unsnap_vector(pps.viewangles, ps.viewangles)
        torsoAnim = ps.torsoAnim = unsnap_num(pps.torsoAnim, ps.torsoAnim)
        legsAnim = ps.legsAnim = unsnap_num(pps.legsAnim, ps.legsAnim)
    if not anim:
        return
    
    doc.SetTime( c4d.BaseTime( frame, fps ) )
    
    doc.SetActiveObject(player_object)
    player_object.SetAbsPos( c4d.Vector( px, pz, py ) )
    c4d.CallCommand( 12410 )
    cam = c4d_find_child(player_object,"1st person")
    doc.SetActiveObject(cam)
    cam.SetAbsRot(c4d.Vector( math.radians(ry-90), math.radians(-rx),0 ))
    c4d.CallCommand( 12410 )
    
    model_object = c4d_find_child(player_object,model)
    model_object.SetAbsRot(c4d.Vector( math.radians(ry+180), math.radians(-rx),0 ))
    head = c4d_find_child(model_object,"models/players/%s/head.md3" % model)
    c4d_animate_md3(doc,fs,0,head)
    upper = c4d_find_child(model_object,"models/players/%s/upper.md3" % model)
    c4d_animate_md3(doc,fs,torsoAnim,upper)
    lower = c4d_find_child(model_object,"models/players/%s/lower.md3" % model)
    c4d_animate_md3(doc,fs,legsAnim,lower)
    
    if ps.weapon != 0:
        c4d_show_weapon(doc,player_object,weapon_id2md3path(ps.weapon))
        
    
def c4d_animate_md3(doc,fs,frame,md3_object):
    #print "%s %d" % (md3_object.GetName(),frame)
    m = fs[md3_object.GetName()]
    if not c4d.IsCommandChecked(PLA_COMMAND):
        c4d.CallCommand(PLA_COMMAND)
    pi = 0
    for s in m.surfaces:
        surface_object = c4d_find_child(md3_object,s.name)
        doc.SetActiveObject(surface_object)
        for i in range(0, s.num_verts):
            surface_object.SetPoint(i, c4d.Vector(s.vertexes[frame][i].x, s.vertexes[frame][i].y, s.vertexes[frame][i].z))
        c4d.CallCommand( 12410 )
        pi += 1
        
    for i in range(0,m.num_tags):
        tag_object = c4d_find_child(md3_object,m.tags[i].name)
        doc.SetActiveObject(tag_object)
        tag = m.tags[frame*m.num_tags +i]
        tag_object.SetRelPos(c4d.Vector(tag.origin.x,tag.origin.y,tag.origin.z))
        tag_object.SetRelRot(c4d_matrix_hpb(tag.axis))
        c4d.CallCommand( 12410 )

def c4d_show_weapon(doc,player_object,name):
    for weapon_object in c4d_find_child(player_object,"weapons").GetChildren():
        if name == weapon_object.GetName():
            weapon_object.SetEditorMode(c4d.MODE_ON)
            weapon_object.SetRenderMode(c4d.MODE_ON)
        else:
            weapon_object.SetEditorMode(c4d.MODE_OFF)
            weapon_object.SetRenderMode(c4d.MODE_OFF)
        doc.SetActiveObject(weapon_object)
        c4d.CallCommand( 12410 )
        
def c4d_animate_weapons(doc,fs,player_object):
    for weapon_object in c4d_find_child(player_object,"weapons").GetChildren():
        c4d_animate_md3(doc,fs,0,weapon_object)
    
def c4d_import_demo(doc,fs,path,offset,fps,step):
    d = demo.read_demo(path)
    doc.SetFps(fps)
    doc.SetMaxTime(c4d.BaseTime(d.time*fps / 1000,30))
    
    p = d.config["player_id_1"]
    player_object = c4d.BaseObject(c4d.Onull)
    player_object.SetName(p["n"])
    c4d_make_player_camera(player_object)
    c4d_make_player_static(fs,player_object,p["model"],d.weapons)
    
    doc.InsertObject(player_object)
    c4d_animate_weapons(doc,fs,player_object)
    pps = None
    pframe = 0
    for ps in d.pstates:
        frame = time2fps(d.starttime, fps, ps.commandTime)
        anim = frame == pframe
        if anim:
            pframe += step
        c4d_animate_player(doc,fs,anim,frame,fps,player_object,p["model"],pps,ps)
        pps = ps

class CachedQFileSystem():
    def __init__(self,path):
        self.fs = QFileSystem(path)
        self.cache = {}
        
    def __getitem__(self,id):
        if not id in self.cache:
            o = self.fs[id]
            self.cache[id] = o
            return o
        return self.cache[id]
    
    def extract(self,what,where):
        self.fs.extract(what,where)

dpath = "C:\Program Files (x86)\Quake III Arena\defrag\demos\coldrun_TR[df.vq3]00.07.576(q3a.scripted).dm_68"
fs = CachedQFileSystem("C:\\Program Files (x86)\\Quake III Arena")
c4d_import_demo(doc,fs,dpath,0,30,5)
c4d.DrawViews(c4d.DRAWFLAGS_FORCEFULLREDRAW)